﻿using UnityEngine;
using System.Collections;

/// <summary>
/// DrawBoard using texture.
/// </summary>
[AddComponentMenu("wsLibrary/GUI/Texture Draw")]
[RequireComponent(typeof(GUITexture))]
public class wsTextureDraw : MonoBehaviour
{
	/// <summary>
	/// texture for draw.
	/// </summary>
	private Texture2D _texture = null;

	/// <summary>
	/// size of texture.
	/// </summary>
	[SerializeField]
	private wsSize textureSize = new wsSize(300, 200);

	/// <summary>
	/// size property of texture.
	/// </summary>
	public wsSize Size { get { return textureSize; } }

	/// <summary>
	/// color of texture.
	/// </summary>
	[SerializeField]
	private Color textureColor = Color.white;

	/// <summary>
	/// color property of texture.
	/// </summary>
	public Color TextureColor { get { return textureColor; } }

	/// <summary>
	/// texture mode.
	/// </summary>
	public enum MODE 
	{ 
		/// <summary>
		/// FIXED_RATIO MODE > x : 0~1, y : 0~1, z : overlap order. higher is front.
		/// </summary>
		FIXED_RATIO, 
		/// <summary>
		/// FIXED_SIZE MODE > (x, y) is pixel position, z : overlap order. higher is front.
		/// </summary>
		FIXED_SIZE 
	};

	/// <summary>
	/// texture mode property.
	/// </summary>
	public MODE positionMode = MODE.FIXED_SIZE;

	/// <summary>
	/// position of gameobject.
	/// </summary>
	[SerializeField]
	private Vector3 texturePosition;

	/// <summary>
	/// position property of gameobject.
	/// </summary>
	public Vector2 Position
	{
		get
		{
			return new Vector2(texturePosition.x, texturePosition.y);
		}
		set
		{
			texturePosition.x = value.x;
			texturePosition.y = value.y;

			SetPosition(texturePosition, positionMode);
		}
	}

	/// <summary>
	/// enable the gameobject.
	/// </summary>
	public bool Enable
	{
		get
		{
			return gameObject.active;
		}
		set
		{
			gameObject.active = value;
		}
	}
	
	void Start()
	{
		CreateTexture((int)textureSize.width, (int)textureSize.height, textureColor);
		SetPosition(texturePosition, positionMode);
	}

	/// <summary>
	/// create gameobject and texture
	/// </summary>
	/// <param name="width">texture width</param>
	/// <param name="height">texture height</param>
	/// <param name="color">texture color</param>
	void CreateTexture(int width, int height, Color color)
	{
		// Create a new 2x2 texture ARGB32 (32 bit with alpha) and no mipmaps
		Texture2D texture = new Texture2D(width, height, TextureFormat.ARGB32, false);

		// set the pixel colors
		for (int w = 0; w < width; w++)
		{
			for (int h = 0; h < height; h++)
			{
				texture.SetPixel(w, h, color);
			}
		}

		// Apply all SetPixel calls
		texture.Apply();

		// connect texture to this._texture.
		this._texture = texture;

		// create gameobject.
		gameObject.guiTexture.texture = texture;
		gameObject.guiTexture.color = Color.white;
	}

	/// <summary>
	/// set position and set size of gameobject.
	/// </summary>
	public void SetPosition(Vector3 position, wsTextureDraw.MODE mode)
	{
		if (mode == wsTextureDraw.MODE.FIXED_RATIO)
		{
			gameObject.transform.position = position;
			gameObject.transform.localScale = new Vector3((float)_texture.width / Screen.width, (float)_texture.height / Screen.height, 1.0f);
		}
		else if (mode == wsTextureDraw.MODE.FIXED_SIZE)
		{
			gameObject.transform.position = new Vector3(0, 0, position.z);
			gameObject.transform.localScale = Vector3.zero;
			gameObject.guiTexture.pixelInset = new Rect(position.x, position.y, _texture.width, _texture.height); // default : 0,0,0,0
		}
	}

	/// <summary>
	/// set tint color
	/// </summary>
	/// <param name="color"></param>
	public void SetColor(Color color)
	{
		gameObject.guiTexture.color = Color.white;
	}

	/// <summary>
	/// set pixel
	/// </summary>
	/// <param name="x"></param>
	/// <param name="y"></param>
	/// <param name="color"></param>
	public void SetPixel(int x, int y, Color color)
	{
		this._texture.SetPixel(x, y, color);
		this._texture.Apply();
	}

	/// <summary>
	/// draw line with 1 thickness.
	/// </summary>
	/// <param name="x0"></param>
	/// <param name="y0"></param>
	/// <param name="x1"></param>
	/// <param name="y1"></param>
	/// <param name="color"></param>
	public void Line(int x0, int y0, int x1, int y1, Color color)
	{
		this.Line(x0, y0, x1, y1, color, 1);
	}

	/// <summary>
	/// draw line with thickness.
	/// </summary>
	/// <param name="x0"></param>
	/// <param name="y0"></param>
	/// <param name="x1"></param>
	/// <param name="y1"></param>
	/// <param name="color"></param>
	/// <param name="thickness"></param>
	public void Line(int x0, int y0, int x1, int y1, Color color, int thickness)
	{
		// Bresenham's line algorithm

		int dx = wsMath.Abs(x1 - x0);
		int dy = wsMath.Abs(y1 - y0);

		int sx = 0;
		int sy = 0;

		if (x0 < x1) sx = 1; else sx = -1;
		if (y0 < y1) sy = 1; else sy = -1;
		int err = dx - dy;

		while (true)
		{
			if (thickness > 1)
			{
				thickness = thickness - 1;

				for (int x = x0 - thickness; x < x0 + thickness; x++)
				{
					for (int y = y0 - thickness; y < y0 + thickness; y++)
					{
						this._texture.SetPixel(x, y, color);
					}
				}
			}
			else
			{
				this._texture.SetPixel(x0, y0, color);
			}

			if (x0 == x1 && y0 == y1)
				break;

			int errX2 = 2 * err;
			if (errX2 > -dy)
			{
				err = err - dy;
				x0 = x0 + sx;
			}
			if (errX2 < dx)
			{
				err = err + dx;
				y0 = y0 + sy;
			}
		}

		this._texture.Apply();
	}

	/// <summary>
	/// AALine
	/// </summary>
	/// <param name="x0"></param>
	/// <param name="y0"></param>
	/// <param name="x1"></param>
	/// <param name="y1"></param>
	/// <param name="color"></param>
	public void AALine(int x0, int y0, int x1, int y1, Color color)
	{
		//int addr = (y0*640+x0)*4;
		int x = x0;
		int y = y0;

		int dx = x1 - x0;
		int dy = y1 - y0;

		int du;
		int dv;
		int u;
		int v;
		int uincr;
		int vincr;

		/* By switching to (u,v), we combine all eight octants */
		if (wsMath.Abs(dx) > wsMath.Abs(dy))
		{

			/* Note: If this were actual C, these integers would be lost
			 * at the closing brace.  That's not what I mean to do.  Do what
			 * I mean. */

			du = wsMath.Abs(dx);
			dv = wsMath.Abs(dy);
			u = x1;
			v = y1;
			uincr = 1;
			vincr = 1;

			if (dx < 0)
				uincr = -uincr;
			if (dy < 0)
				vincr = -vincr;

		}
		else
		{
			du = wsMath.Abs(dy);
			dv = wsMath.Abs(dx);
			u = y1;
			v = x1;
			uincr = 1;
			vincr = 1;

			if (dy < 0)
				uincr = -uincr;
			if (dx < 0)
				vincr = -vincr;

		}

		int uend = u + 2 * du;
		int d = (2 * dv) - du;      /* Initial value as in Bresenham's */
		int incrS = 2 * dv;     /* &#916;d for straight increments */
		int incrD = 2 * (dv - du);      /* &#916;d for diagonal increments */
		int twovdu = 0; /* Numerator of distance; starts at 0 */
		double invD = 1.0 / ((2.0 * Mathf.Sqrt((float)(du * du + dv * dv))));   /* Precomputed inverse denominator */
		double invD2du = 2.0 * (du * invD);   /* Precomputed constant */

		do
		{
			/* Note: this pseudocode doesn't ensure that the address is
			 * valid, or that it even represents a pixel on the same side of
			 * the screen as the adjacent pixel */

			DrawPixelD(x, y, twovdu * invD);
			DrawPixelD(x, y + vincr, invD2du - twovdu * invD);
			DrawPixelD(x, y - vincr, invD2du + twovdu * invD);

			if (d < 0)
			{
				/* choose straight (u direction) */
				twovdu = d + du;
				d = d + incrS;
			}
			else
			{
				/* choose diagonal (u+v direction) */
				twovdu = d - du;
				d = d + incrD;
				v = v + 1;
				y = y + vincr;
			}

			u = u + 1;
			x = x + uincr;

		} while (u < uend);

		this._texture.Apply();
	}

	/// <summary>
	/// draw pixel with distance.
	/// </summary>
	/// <param name="x"></param>
	/// <param name="y"></param>
	/// <param name="dist"></param>
	public void DrawPixelD(int x, int y, double dist)
	{
		double normDist = 1.0 - ((2.0 / 3.0) * dist);
		//normDist *= normDist;
		int alpha = (int)(255.0 * normDist);

		int r1 = 111;
		int g1 = 222;
		int b1 = 0;

		int r2 = 0;
		int g2 = 55;
		int b2 = 0;
		double factor = (alpha / 255.0);

		int red = (int)((1 - factor) * r2 + (factor * r1));
		int green = (int)((1 - factor) * g2 + (factor * g1));
		int blue = (int)((1 - factor) * b2 + (factor * b1));

		SetPixel(x, y, new Color(red, green, blue, 1));
	}
}
